package typeCheck;

import gen.GoParser;
import gen.GoParserBaseListener;
import symbolTable.*;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.ParseTreeProperty;
import org.antlr.v4.runtime.tree.TerminalNode;

import java.util.*;

/**
 *The member variables of the grammar checker are the same as those of the symbol table,
 * except that there is an additional type table to check for type problems.
 * The main job of the grammar checker is to check for errors, including
 * using undefined symbols,
 * mixing function symbols, variable symbols, type symbols,
 * type errors (such as assigning a string expression to an int variable),
 * it also adds type attributes to all variable symbols
 */
public class TypeChecker extends GoParserBaseListener {

    private final ParseTreeProperty<Scope> scopes;

    private final GlobalScope globalScope;

    private Scope currentScope;



    private LinkedHashMap<String, Type> typeTable = new LinkedHashMap<String, Type>();

    public TypeChecker(ParseTreeProperty<Scope> scopes, GlobalScope globalScope) {
        this.scopes = scopes;
        this.globalScope = globalScope;

        // add fundamental type in type table
        this.typeTable.put("int", new FundamentalType("int"));
        this.typeTable.put("int8", new FundamentalType("int8"));
        this.typeTable.put("int16", new FundamentalType("int16"));
        this.typeTable.put("int32", new FundamentalType("int32"));
        this.typeTable.put("int64", new FundamentalType("int64"));
        this.typeTable.put("uint", new FundamentalType("uint"));
        this.typeTable.put("uint8", new FundamentalType("uint8"));
        this.typeTable.put("uint16", new FundamentalType("uint16"));
        this.typeTable.put("uint32", new FundamentalType("uint32"));
        this.typeTable.put("uint64", new FundamentalType("uint64"));
        this.typeTable.put("float32", new FundamentalType("float32"));
        this.typeTable.put("float64", new FundamentalType("float64"));
        this.typeTable.put("complex64", new FundamentalType("complex64"));
        this.typeTable.put("complex128", new FundamentalType("complex128"));
        this.typeTable.put("string", new FundamentalType("string"));
        this.typeTable.put("bool", new FundamentalType("bool"));
    }

    /**
     * Define the symbols in the parameters into the function scope
     *
     * @param ctx        ParametersContext
     * @param func    functionSymbol to define
     */
    private void parseParameters(GoParser.ParametersContext ctx, FunctionType func) {
        // get all parameter declaration
        List<GoParser.ParameterDeclContext> paramCtxs = ctx.parameterDecl();

        // Process parameter declaration separately
        for (GoParser.ParameterDeclContext paramCtx : paramCtxs) {

            String typeName = SymbolTable.getEmbeddedType_(paramCtx.type_()).getText();
            Type type = getType(SymbolTable.getEmbeddedType_(paramCtx.type_()));
            // process every parameter in parameter declaration
            for (int i = 0; i < paramCtx.IDENTIFIER().size(); i++) {
                if (ctx.getParent() instanceof GoParser.ResultContext) {
                    func.addResult(typeName, type);
                }
                else {
                    func.addParameter(typeName, type);
                }
            }
        }
    }


    private Symbol searchSymbol(String name, Scope scope) {
        if (scope.search(name) != null)
            return scope.search(name);

        if (scope.getEnclosingScope() != null)
            return searchSymbol(name, scope.getEnclosingScope());

        return null;
    }

    private List<Type> makeListOfType(Type...types) {
        return new LinkedList<Type>(Arrays.asList(types));
    }

    private <K, T> T getElementFromMap(Map<K, T> relatedType, int index) {
        if (index >= relatedType.size()) {
            return  null;
        }
        Set<K> keySet = relatedType.keySet();
        int tmp = 0;
        for (K key : keySet) {
            if (tmp++ == index)
                return relatedType.get(key);
        }
        return null;
    }

    /**
     * Error reporting method
     * @param token wrong token
     * @param msg   reporting message
     */
    public static void error(Token token, String msg) {
        System.err.printf("line %d:%d %s\n", token.getLine(), token.getCharPositionInLine(), msg);
    }

    /**
     * get a type symbol which type_Context represent
     * @param ctx   type_Context
     * @return  result of type if parse successfully, null else
     */
    private Type getType(GoParser.Type_Context ctx) {
        if (ctx.typeName() != null) {   // match rule 2
            return getTypeFromTypeName(ctx.typeName());
        } else {
            return getTypeFromTypeLit(ctx.typeLit());
        }
    }

    private Type getTypeFromTypeLit(GoParser.TypeLitContext ctx) {
        // match rule 1.1
        if (ctx.arrayType() != null)
            return getTypeFromArrayType(ctx.arrayType());

        // match rule 1.2
        if (ctx.structType() != null)
            return getTypeFromStructType(ctx.structType());

        // match rule 1.3
        if (ctx.pointerType() != null)
            return getTypeFromPointerType(ctx.pointerType());

        // match rule 1.4
        if (ctx.functionType() != null)
            return getTypeFromFunctionType(ctx.functionType());

        // match rule 1.5
        if (ctx.interfaceType() != null)
            return getTypeFromInterfaceType(ctx.interfaceType());

        // match rule 1.6
        if (ctx.sliceType() != null)
            return getTypeFromSliceType(ctx.sliceType());

        // match rule 1.7
        if (ctx.mapType() != null)
            return getTypeFromMapType(ctx.mapType());

        // match rule 1.8
        return getTypeFromChannelType(ctx.channelType());
    }

    private Type getTypeFromFunctionType(GoParser.FunctionTypeContext ctx) {
        FunctionType func = new FunctionType(ctx.getText());

        parseParameters(ctx.signature().parameters(), func);

        if (ctx.signature().result() != null) {
            if (ctx.signature().result().parameters() != null) {
                parseParameters(ctx.signature().result().parameters(), func);
            } else {
                func.addResult(SymbolTable.getEmbeddedType_(ctx.signature().result().type_()).getText(), getType(SymbolTable.getEmbeddedType_(ctx.signature().result().type_())));
            }
        }

        this.currentScope = this.currentScope.getEnclosingScope();
        return func;
    }

    private Type getTypeFromMapType(GoParser.MapTypeContext ctx) {
        return new MapType(ctx.getText(), getType(SymbolTable.getEmbeddedType_(ctx.type_())), getType(SymbolTable.getEmbeddedType_(ctx.elementType().type_())));
    }

    private Type getTypeFromChannelType(GoParser.ChannelTypeContext ctx) {
        Type elementType = getType(SymbolTable.getEmbeddedType_(ctx.elementType().type_()));
        if (elementType == null) {
            // parse fail
            return null;
        }

        ChannelType channel = null;
        if (ctx.RECEIVE() == null) {
            channel = new ChannelType(ctx.getText(), ChannelType.ChannelTypes.NORMAL, elementType);
        } else if (ctx.getStart().getType() == GoParser.RECEIVE) {
            channel = new ChannelType(ctx.getText(), ChannelType.ChannelTypes.SEND, elementType);
        } else {
            channel = new ChannelType(ctx.getText(), ChannelType.ChannelTypes.RECEIVE, elementType);
        }

        return channel;
    }

    /**
     * parse a sliceType
     * @param ctx   sliceTypeContext
     * @return  result of type if parse successfully, null else
     */
    private Type getTypeFromSliceType(GoParser.SliceTypeContext ctx) {
        // get element type
        Type elementType = getType(SymbolTable.getEmbeddedType_(ctx.elementType().type_()));
        if (elementType == null) {
            // parse fail
            return null;
        }

        // return a slice type symbol whose relatedType is element type
        return new ArrayType(ctx.getText(), elementType);
    }

    /**
     * parse a interfaceType
     * @param ctx   interfaceTypeContext
     * @return  result of type if parse successfully, null else
     */
    private Type getTypeFromInterfaceType(GoParser.InterfaceTypeContext ctx) {
        // get all method and typeName declare in interface
        List<GoParser.MethodSpecContext> methods = ctx.methodSpec();

        // return an interface type symbol whose relatedType are methods and typeNames
        InterfaceType interfaceType = new InterfaceType(ctx.getText());

        // declare methods
        for (GoParser.MethodSpecContext methodSpec : methods) {
            String methodName = methodSpec.IDENTIFIER().getText();
            FunctionType func = new FunctionType(methodName);

            // declare parameters and result
            parseParameters(methodSpec.parameters(), func);
            if (methodSpec.result() != null) {
                if (methodSpec.result().parameters() != null) {
                    parseParameters(methodSpec.result().parameters(), func);
                } else {
                    func.addResult(SymbolTable.getEmbeddedType_(methodSpec.result().type_()).getText(), getType(SymbolTable.getEmbeddedType_(methodSpec.result().type_())));
                }
            }
            interfaceType.addMethod(func);
        }

        return interfaceType;
    }

    /**
     * parse a pointerType
     * @param ctx   pointerTypeContext
     * @return  result of type if parse successfully, null else
     */
    private Type getTypeFromPointerType(GoParser.PointerTypeContext ctx) {
        // get element type
        if (getType(SymbolTable.getEmbeddedType_(ctx.type_())) == null) { // parse fail
            return null;
        }

        // return a pointer type whose relatedType is element type
        return new PointerType(ctx.getText(), getType(SymbolTable.getEmbeddedType_(ctx.type_())));
    }

    /**
     * parse a structType
     * @param ctx   structTypeContext
     * @return  result of type if parse successfully, null else
     */
    private Type getTypeFromStructType(GoParser.StructTypeContext ctx) {
        // return a struct type symbol whose relatedType are struct fields
        StructType struct = new StructType(ctx.getText());

        // get all fields and add them to relatedType
        List<GoParser.FieldDeclContext> fields = ctx.fieldDecl();
        for (GoParser.FieldDeclContext field : fields) {
            if (field.type_() != null) {    // match rule 1
                Type fieldType = getType(SymbolTable.getEmbeddedType_(field.type_()));
                if (fieldType == null) {    // parse fail
                    return null;
                }

                // add fields to relatedType
                for (TerminalNode fieldClause : field.IDENTIFIER()) {
                    struct.addAttribute(fieldType);
                    struct.setFiledNum(struct.getFiledNum() + 1);
                }
            } else if (field.embeddedField().STAR() != null) {  // match rule 2.1
                // get pointer element type
                Type elementType = getTypeFromTypeName(field.embeddedField().typeName());
                if (elementType == null) {
                    // parse fail
                    return null;
                }

                // create a pointer type whose relatedType is element type
                PointerType fieldType = new PointerType(field.embeddedField().getText(), elementType);

                // add pointer type to relatedType
                struct.addAttribute(fieldType);
            } else {    // match rule 2.2
                // get typeName type
                Type fieldType = getTypeFromTypeName(field.embeddedField().typeName());
                if (fieldType == null) {
                    // parse fail
                    return null;
                }

                // add pointer type to relatedType
                struct.addAttribute(fieldType);
                struct.setFiledNum(struct.getFiledNum() + 1);
            }
        }
        return struct;
    }

    /**
     * parse a arrayType
     * @param ctx   arrayTypeContext
     * @return  result of type if parse successfully, null else
     */
    private Type getTypeFromArrayType(GoParser.ArrayTypeContext ctx) {
        // get element type
        if (getType(SymbolTable.getEmbeddedType_(ctx.elementType().type_())) == null) {
            // parse fail
            return null;
        }

        // return an array type whose relatedType is element type
        return new ArrayType(ctx.getText(), getType(SymbolTable.getEmbeddedType_(ctx.elementType().type_())));
    }

    /**
     * parse typeName
     * @param ctx   typeNameContext
     * @return  result of type if parse successfully, null else
     */
    private Type getTypeFromTypeName(GoParser.TypeNameContext ctx) {
        // it must have been declared
        if (ctx.qualifiedIdent() != null) {
            if (this.typeTable.containsKey(ctx.qualifiedIdent().IDENTIFIER(0).getText())) {
                if (this.typeTable.get(ctx.qualifiedIdent().IDENTIFIER(0).getText()).symbolType == TypeEnum.STRUCT) {
                    StructType struct = (StructType) this.typeTable.get(ctx.qualifiedIdent().IDENTIFIER(0).getText());
                    if (struct.getAttributes().containsKey(ctx.qualifiedIdent().IDENTIFIER(1).getText())) {
                        return struct.getAttributes().get(ctx.qualifiedIdent().IDENTIFIER(1).getText());
                    }
                    error(ctx.qualifiedIdent().IDENTIFIER(1).getSymbol(), ctx.qualifiedIdent().IDENTIFIER(1).getText() + " is not a member of " + ctx.qualifiedIdent().IDENTIFIER(0).getText());
                    return null;
                }
                error(ctx.qualifiedIdent().IDENTIFIER(0).getSymbol(), ctx.qualifiedIdent().IDENTIFIER(0) + " is not a struct type");
                return null;
            }
            error(ctx.qualifiedIdent().IDENTIFIER(0).getSymbol(), "there is no type named " + ctx.qualifiedIdent().IDENTIFIER(0));
            return null;
        }
        if (ctx.IDENTIFIER() != null && this.typeTable.containsKey(ctx.IDENTIFIER().getText()))
            return this.typeTable.get(ctx.IDENTIFIER().getText());

        error(ctx.IDENTIFIER().getSymbol(), "there is no type named " + ctx.qualifiedIdent().IDENTIFIER());
        return null;
    }

    private List<Type> getTypesFromExpression(GoParser.ExpressionContext ctx) {
        if (ctx.primaryExpr() != null) {
            return getTypesFromPrimaryExpr(ctx.primaryExpr());
        }
        List<Type> type1 = getTypesFromExpression(ctx.expression(0));

        if (type1 == null || type1.size() > 1) {
            // parse fail or expression has many parse result
            error(ctx.expression(0).getStart(), ctx.expression(0).getText() + " parse fail or has too many return values");
            return null;
        }

        if (ctx.unary_op != null) {
            return getTypeFromUnaryOpExpr(ctx, type1);
        }

        // get result of two expressions parse
        List<Type> type2 = getTypesFromExpression(ctx.expression(1));

        if (type2 == null || type2.size() > 1) {
            // parse fail or expression has many parse result
            error(ctx.expression(1).getStart(), ctx.expression(1).getText() + " parse fail or has too many return values");
            return null;
        }

        if (ctx.mul_op != null) {
            return getTypeFromMulOpExpr(ctx, type1, type2);
        }
        if (ctx.add_op != null) {
            return getTypeFromAddOpExpr(ctx, type1, type2);
        }
        if (ctx.rel_op != null) {
            return getTypeFromRelOpExpr(ctx, type1, type2);
        }
        return getTypeFromLogicalExpr(ctx, type1,type2);
    }

    private List<Type> getTypeFromUnaryOpExpr(GoParser.ExpressionContext ctx, List<Type> type1) {
        switch (ctx.unary_op.getType()) {
            case GoParser.PLUS :
            case GoParser.MINUS : {
                // PLUS and MINUS can only use on float number or integer number
                if (Type.intSet.contains(type1.get(0).name) || Type.floatSet.contains(type1.get(0).name))
                    return type1;

                // parse fail
                error(ctx.expression(0).getStart(), ctx.expression(0).getText() + " return value is invalid");
                return null;
            }
            case GoParser.EXCLAMATION : {
                if (type1.get(0).symbolType != TypeEnum.FUNDAMENTAL || !Objects.equals(type1.get(0).name, "bool")) {
                    // parse fail: ! can only use for bool expression
                    error(ctx.expression(0).getStart(), ctx.expression(0).getText() + " is not a bool expression");
                    return null;
                }

                // parse success and return type1
                return type1;
            }
            case GoParser.STAR : {
                if (type1.get(0).symbolType != TypeEnum.POINTER) {
                    // parse fail: * can only use for pointer expression to get data it refers to
                    error(ctx.expression(0).getStart(), ctx.expression(0).getText() + " is not a pointer expression");
                    return null;
                }

                PointerType pointer = (PointerType)type1.get(0);
                // return the element type
                return makeListOfType(pointer.getPointedType());
            }
            case GoParser.AMPERSAND : {
                // & can use for all expression to get its location
                PointerType pointer = new PointerType("*" + type1.get(0).name, type1.get(0));

                // parse success and return a pointer type
                return makeListOfType(pointer);
            }

            case GoParser.RECEIVE : {
                if (type1.get(0).symbolType == TypeEnum.CHANNEL) {
                    ChannelType channel = (ChannelType) type1.get(0);
                    if (channel.getChannelTypes() == ChannelType.ChannelTypes.SEND) {
                        // parse fail : <- can only use for channel expression to get its element
                        error(ctx.expression(0).getStart(), ctx.expression(0).getText() + " is a send only channel expression");
                        return null;
                    }

                    // parse success and return the channel's element type
                    return makeListOfType(channel.getElementType());
                }

                error(ctx.expression(0).getStart(), ctx.expression(0).getText() + " is not a channel expression");
                return null;
            }
            default : return null;
        }
    }

    private List<Type> getTypeFromMulOpExpr(GoParser.ExpressionContext ctx, List<Type> type1, List<Type> type2) {
        switch (ctx.mul_op.getType()) {
            case GoParser.STAR :
            case GoParser.DIV : {
                // * and / cna only use for float number or integer number
                if (Type.intSet.contains(type1.get(0).name) || Type.floatSet.contains(type1.get(0).name)) {
                    if (Type.intSet.contains(type2.get(0).name) || Type.floatSet.contains(type2.get(0).name)) {
                        // if one expression is float number, result is float number
                        if (Type.floatSet.contains(type2.get(0).name))
                            return type2;
                        return type1;
                    }
                    error(ctx.expression(1).getStart(), ctx.expression(1).getText() + " is not a float or integer expression");
                    return null;
                }
                error(ctx.expression(0).getStart(), ctx.expression(0).getText() + " is not a float or integer expression");
                return null;
            }
            case GoParser.MOD :
            case GoParser.LSHIFT :
            case GoParser.RSHIFT :
            case GoParser.AMPERSAND :
            case GoParser.BIT_CLEAR : {
                // parse fail : % and << and >> and BIT_CLEAR can only use for two integer number
                if (!Type.intSet.contains(type2.get(0).name)) {
                    if (!Type.intSet.contains(type1.get(0).name)) {
                        error(ctx.expression(0).getStart(), ctx.expression(0).getText() + " is not an integer expression");
                        return null;
                    }
                    error(ctx.expression(1).getStart(), ctx.expression(1).getText() + " is not an integer expression");
                    return null;
                }

                // parse success and return type1
                return type1;
            }
            default : return null;
        }
    }

    private List<Type> getTypeFromAddOpExpr(GoParser.ExpressionContext ctx, List<Type> type1, List<Type>type2) {
        switch (ctx.add_op.getType()) {
            case GoParser.OR :
            case GoParser.CARET : {
                // parse fail : | and ^ can only use for two integer number
                if (!Type.intSet.contains(type2.get(0).name)) {
                    if (!Type.intSet.contains(type1.get(0).name)) {
                        error(ctx.expression(0).getStart(), ctx.expression(0).getText() + " is not an integer expression");
                        return null;
                    }
                    error(ctx.expression(1).getStart(), ctx.expression(1).getText() + " is not an integer expression");
                    return null;
                }

                // parse success and return type1
                return type1;
            }
            case GoParser.PLUS :
            case GoParser.MINUS : {
                // + and - cna only use for float number or integer number
                if (Type.intSet.contains(type1.get(0).name) || Type.floatSet.contains(type1.get(0).name)) {
                    if (Type.intSet.contains(type2.get(0).name) || Type.floatSet.contains(type2.get(0).name)) {
                        // if one expression is float number, result is float number
                        if (Type.floatSet.contains(type2.get(0).name))
                            return type2;
                        return type1;
                    }
                    error(ctx.expression(1).getStart(), ctx.expression(1).getText() + " is not a float or integer expression");
                    return null;
                }
                error(ctx.expression(0).getStart(), ctx.expression(0).getText() + " is not a float or integer expression");
                return null;
            }
            default : return null;
        }
    }

    private List<Type> getTypeFromRelOpExpr(GoParser.ExpressionContext ctx, List<Type> type1, List<Type> type2) {
        switch (ctx.rel_op.getType()) {
            case GoParser.LESS :
            case GoParser.LESS_OR_EQUALS :
            case GoParser.GREATER :
            case GoParser.GREATER_OR_EQUALS : {
                // < and <= and > and >= cna only use for float number and integer number
                if (Type.intSet.contains(type1.get(0).name) || Type.floatSet.contains(type1.get(0).name)) {
                    if (Type.intSet.contains(type2.get(0).name) || Type.floatSet.contains(type2.get(0).name)) {
                        // return a bool type
                        return makeListOfType(this.typeTable.get("bool"));
                    }
                    error(ctx.expression(1).getStart(), ctx.expression(1).getText() + " is not a float or integer expression");
                    return null;
                }
                error(ctx.expression(0).getStart(), ctx.expression(0).getText() + " is not a float or integer expression");
                // parse fail
                return null;
            }
            case GoParser.EQUALS :
            case GoParser.NOT_EQUALS : {
                if (Type.comparable(type1.get(0), type2.get(0))) {
                    // return a bool type
                        return makeListOfType(this.typeTable.get("bool"));
                }
                error(ctx.expression(0).getStart(), ctx.expression(0).getText() + " and " + ctx.expression(1).getText() + " are not the same type expressions");
                return null;
            }
            default : return null;
        }
    }

    private List<Type> getTypeFromLogicalExpr(GoParser.ExpressionContext ctx, List<Type> type1, List<Type> type2) {
        if (Objects.equals(type1.get(0).name, "bool")) {
            if (Objects.equals(type2.get(0).name, "bool")) {
                // return a bool type
                return makeListOfType(this.typeTable.get("bool"));
            }
            // parse fail : && and || can only use for two bool expressions
            error(ctx.expression(1).getStart(), ctx.expression(1).getText() + " is not a bool expression");
            return null;
        }
        error(ctx.expression(0).getStart(), ctx.expression(0).getText() + " is not a bool expression");
        return null;
    }

    private List<Type> getTypesFromPrimaryExpr(GoParser.PrimaryExprContext ctx) {
        if (ctx.operand() != null) {
            return getTypesFromOperand(ctx.operand());
        }
        if (ctx.methodExpr() != null) {
            return getTypesFromMethodExpr(ctx.methodExpr());
        }

        List<Type> type1 = getTypesFromPrimaryExpr(ctx.primaryExpr());

        if (type1 == null || type1.size() > 1) {
            // parse fail or expression has many parse result
            error(ctx.primaryExpr().getStart(), ctx.primaryExpr().getText() + " parse fail or has too many return values");
            return null;
        }

        if (ctx.DOT() != null) {
            return getTypeFromDotExpr(ctx, type1);
        }
        if (ctx.index() != null) {
            return getTypeFromIndexExpr(ctx, type1);
        }
        if (ctx.slice_() != null) {
            return getTypeFromSliceExpr(ctx, type1);
        }
        if (ctx.typeAssertion() != null) {
            return getTypeFromTypeAssertionExpr(ctx, type1);
        }
        return getTypeFromFunctionCall(ctx, type1);
    }

    private List<Type> getTypeFromDotExpr(GoParser.PrimaryExprContext ctx, List<Type> type1) {
        // it should be : (struct).(one of relatedTypes)
        if (type1.get(0).symbolType != TypeEnum.STRUCT){
            error(ctx.primaryExpr().getStart(), ctx.primaryExpr().getText() + " is not a struct type expression");
            return null;
        }
        StructType struct = (StructType) type1.get(0);
        if (!struct.getAttributes().containsKey(ctx.IDENTIFIER().getText())) {
            error(ctx.IDENTIFIER().getSymbol(), ctx.primaryExpr().getText() + " has no member named " + ctx.IDENTIFIER().getText());
            return null;
        }

        // parse success
        return makeListOfType(struct.getAttributes().get(ctx.IDENTIFIER().getText()));
    }

    private List<Type> getTypeFromIndexExpr(GoParser.PrimaryExprContext ctx, List<Type> type1) {
        // parse index expression first
        List<Type> type2 = getTypesFromExpression(ctx.index().expression());
        if (type2 == null || type2.size() > 1) {
            error(ctx.index().getStart(), ctx.index().getText() + " parse fail or has too much return values");
            return null;
        }

        if (type1.get(0).symbolType != TypeEnum.ARRAY) {
            error(ctx.primaryExpr().getStart(), ctx.primaryExpr().getText() + " is not an array type expression");
            return null;
        }

        if (!Type.intSet.contains(type2.get(0).name)) {
            error(ctx.index().expression().getStart(), ctx.index().expression().getText() + " is not an integer type expression");
            return null;
        }

        // parse success
        ArrayType arrayType = (ArrayType) type1.get(0);
        return makeListOfType(arrayType.getElementType());
    }

    private List<Type> getTypeFromSliceExpr(GoParser.PrimaryExprContext ctx, List<Type> type1) {
        if (type1.get(0).symbolType != TypeEnum.ARRAY) {
            error(ctx.primaryExpr().getStart(), ctx.primaryExpr().getText() + " is not an array type expression");
            return null;
        }

        List<GoParser.ExpressionContext> exprs = ctx.slice_().expression();
        for (GoParser.ExpressionContext expr : exprs) {
            List<Type> type2 = getTypesFromExpression(expr);
            if (type2 == null || type2.size() > 1) {
                error(expr.getStart(), expr.getText() + " parse fail or has too many return values");
                return null;
            }
            if (!Type.intSet.contains(type2.get(0).name)) {
                error(expr.getStart(), expr.getText() + " is not an integer type expression");
                return null;
            }
        }

        // parse success
        return type1;
    }

    private List<Type> getTypeFromTypeAssertionExpr(GoParser.PrimaryExprContext ctx, List<Type> type1) {
        // it should be like : (struct).(interface)
        GoParser.Type_Context desType = SymbolTable.getEmbeddedType_(ctx.typeAssertion().type_());
        Type type2 = getType(desType);

        if (type2 == null) {
            error(desType.getStart(), desType.getText() + " parse fail");
            return null;
        }

        if (type1.get(0).symbolType == TypeEnum.STRUCT){
            if (type2.symbolType == TypeEnum.INTERFACE) {
                // parse success
                return makeListOfType(type2, this.typeTable.get("bool"));
            }
            error(desType.getStart(), desType.getText() + " is not an interface type expression");
            return null;
        }
        error(ctx.primaryExpr().getStart(), ctx.primaryExpr().getText() + " is not a struct type expression");
        return null;
    }

    private List<Type> getTypeFromFunctionCall(GoParser.PrimaryExprContext ctx, List<Type> type1) {
        if (type1.get(0).symbolType != TypeEnum.FUNCTION) {
            error(ctx.primaryExpr().getStart(), ctx.primaryExpr().getText() + " is not a function type expression");
            return null;
        }

        FunctionType func = (FunctionType) type1.get(0);

        if (ctx.arguments().expressionList() != null) {
            List<GoParser.ExpressionContext> args = ctx.arguments().expressionList().expression();
            if (func.getParameters().size() != args.size()) {
                error(ctx.arguments().expressionList().getStart(), "The number of parameters does not match");
                return null;
            }

            for (int i = 0; i < args.size(); ++i) {
                List<Type> type= getTypesFromExpression(args.get(i));
                if (type == null || type.size() > 1) {
                    error(args.get(i).getStart(), args.get(i).getText() + " parse fail or has too many return values");
                    return null;
                }

                if (!Objects.equals(Objects.requireNonNull(getElementFromMap(func.getParameters(), i)).name, type.get(0).name)) {
                    error(args.get(i).getStart(), "The parameter types do not match");
                    return null;
                }
            }
        }

        // result is a type
        if (func.getResults().size() != 0) {
            List<Type> result = new LinkedList<>();
            for (Map.Entry<String, Type> entry : func.getResults().entrySet())
                result.add(entry.getValue());
            return result;
        }

        return null;
    }

    private List<Type> getTypesFromMethodExpr(GoParser.MethodExprContext ctx) {
        // it should be like : (struct).(member)
        Type type = getTypeFromTypeLit(SymbolTable.getEmbeddedNonNamedType(ctx.nonNamedType()).typeLit());
        if (type == null) {
            error(SymbolTable.getEmbeddedNonNamedType(ctx.nonNamedType()).getStart(), SymbolTable.getEmbeddedNonNamedType(ctx.nonNamedType()).getText() + " parse fail");
            return null;
        }
        if (type.symbolType != TypeEnum.STRUCT) {
            error(SymbolTable.getEmbeddedNonNamedType(ctx.nonNamedType()).getStart(), SymbolTable.getEmbeddedNonNamedType(ctx.nonNamedType()).getText() + " is not a struct type");
            return null;
        }

        // check whether a member of struct has the method
        StructType struct = (StructType) type;
        Set<Map.Entry<String, Type>> entrySet = struct.getAttributes().entrySet();
        for (Map.Entry<String, Type> entry : entrySet) {
            if (entry.getValue().symbolType == TypeEnum.STRUCT) {
                StructType tmp = (StructType) entry.getValue();
                Set<Map.Entry<String, Type>> subEntrySet = tmp.getAttributes().entrySet();
                for (Map.Entry<String, Type> subEntry : subEntrySet) {
                    if (Objects.equals(subEntry.getKey(), ctx.IDENTIFIER().getText()) && subEntry.getValue().symbolType == TypeEnum.FUNCTION) {
                        // parse success
                        return makeListOfType(subEntry.getValue());
                    }
                }
            }
        }

        error(SymbolTable.getEmbeddedNonNamedType(ctx.nonNamedType()).getStart(), SymbolTable.getEmbeddedNonNamedType(ctx.nonNamedType()).getText() + " has no method named " + ctx.IDENTIFIER().getText());
        return null;
    }

    private List<Type> getTypesFromOperand(GoParser.OperandContext ctx) {
        if (ctx.literal() != null)
            return getTypesFromLiteral(ctx.literal());

        if (ctx.operandName() != null)
            return getTypesFromOperandName(ctx.operandName());

        return getTypesFromExpression(ctx.expression());
    }

    private List<Type> getTypesFromLiteral(GoParser.LiteralContext ctx) {
        if (ctx.basicLit() != null)
            return getTypesFromBasicLit(ctx.basicLit());

        if (ctx.compositeLit() != null)
            return getTypesFromCompositeLit(ctx.compositeLit());

        return getTypesFromFunctionLit(ctx.functionLit());
    }

    private List<Type> getTypesFromBasicLit(GoParser.BasicLitContext ctx) {
        if (ctx.NIL_LIT() != null)
            return null;

        if (ctx.integer() != null)
            return makeListOfType(this.typeTable.get("int"));

        if (ctx.string_() != null)
            return makeListOfType(this.typeTable.get("string"));

        return makeListOfType(this.typeTable.get("float64"));
    }

    private List<Type> getTypesFromCompositeLit(GoParser.CompositeLitContext ctx) {
        //TODO:
        return null;
    }

    private Type getTypeFromLiteralType(GoParser.LiteralTypeContext ctx) {
        if (ctx.structType() != null)
            return getTypeFromStructType(ctx.structType());

        if (ctx.arrayType() != null)
            return getTypeFromArrayType(ctx.arrayType());

        if (ctx.elementType() != null)
            return new ArrayType(ctx.getText(), getType(SymbolTable.getEmbeddedType_(ctx.elementType().type_())));

        if (ctx.sliceType() != null)
            return getTypeFromSliceType(ctx.sliceType());

        if (ctx.mapType() != null)
            return getTypeFromMapType(ctx.mapType());

        return getTypeFromTypeName(ctx.typeName());
    }

    private List<Type> getTypesFromFunctionLit(GoParser.FunctionLitContext ctx) {
        FunctionType func = new FunctionType(ctx.getText());

        parseParameters(ctx.signature().parameters(), func);

        if (ctx.signature().result() != null) {
            if (ctx.signature().result().type_() != null) {
                func.addResult(SymbolTable.getEmbeddedType_(ctx.signature().result().type_()).getText(), getType(SymbolTable.getEmbeddedType_(ctx.signature().result().type_())));
            } else {
                parseParameters(ctx.signature().result().parameters(), func);
            }
        }

        return makeListOfType(func);
    }

    private List<Type> getTypesFromOperandName(GoParser.OperandNameContext ctx) {
        Symbol symbol = searchSymbol(ctx.IDENTIFIER().getText(), this.currentScope);
        if (symbol == null) {
            error(ctx.IDENTIFIER().getSymbol(), ctx.IDENTIFIER().getText() + " has not been declared");
            return null;
        }
        return makeListOfType(symbol.type);
    }

    @Override
    public void enterSourceFile(GoParser.SourceFileContext ctx) {
        this.currentScope = this.globalScope;
    }

    @Override
    public void enterFunctionDecl(GoParser.FunctionDeclContext ctx) {
        FunctionType func = new FunctionType(ctx.IDENTIFIER().getText());

        parseParameters(ctx.signature().parameters(), func);

        if (ctx.signature().result() != null) {
            if (ctx.signature().result().parameters() != null) {
                parseParameters(ctx.signature().result().parameters(), func);
            } else {
                func.addResult(SymbolTable.getEmbeddedType_(ctx.signature().result().type_()).getText(), getType(SymbolTable.getEmbeddedType_(ctx.signature().result().type_())));
            }
        }

        FunctionSymbol newFunc = (FunctionSymbol) this.currentScope.search(ctx.IDENTIFIER().getText());
        newFunc.type = func;
        this.currentScope.define(newFunc);
        this.currentScope = this.scopes.get(ctx);
    }

    @Override
    public void exitFunctionDecl(GoParser.FunctionDeclContext ctx) {
        this.currentScope = this.currentScope.getEnclosingScope();
    }

    @Override
    public void enterMethodDecl(GoParser.MethodDeclContext ctx) {
        if (ctx.receiver().parameters().parameterDecl().size() != 1) {
            error(ctx.receiver().parameters().getStart(), ctx.IDENTIFIER().getText() + "has too many receiver");
        }

        if (getType(SymbolTable.getEmbeddedType_(ctx.receiver().parameters().parameterDecl(0).type_())).symbolType != TypeEnum.STRUCT) {
            error(SymbolTable.getEmbeddedType_(ctx.receiver().parameters().parameterDecl(0).type_()).getStart(),
                    SymbolTable.getEmbeddedType_(ctx.receiver().parameters().parameterDecl(0).type_()).getText() + "is not a struct type");
        }

        StructType struct = (StructType) getType(SymbolTable.getEmbeddedType_(ctx.receiver().parameters().parameterDecl(0).type_()));

        FunctionType func = new FunctionType(ctx.IDENTIFIER().getText());
        parseParameters(ctx.signature().parameters(), func);
        if (ctx.signature().result() != null) {
            if (ctx.signature().result().parameters() != null)
                parseParameters(ctx.signature().result().parameters(), func);
            else
                func.addResult(SymbolTable.getEmbeddedType_(ctx.signature().result().type_()).getText(), getType(SymbolTable.getEmbeddedType_(ctx.signature().result().type_())));
        }

        struct.addMethod(func);

        String symbolName = ctx.IDENTIFIER().getText() + "_" + SymbolTable.getEmbeddedType_(ctx.receiver().parameters().parameterDecl(0).type_()).getText();
        FunctionSymbol newFunc = (FunctionSymbol) this.currentScope.search(symbolName);
        newFunc.type = func;
        this.currentScope.define(newFunc);
        this.currentScope = this.scopes.get(ctx);
    }

    @Override
    public void exitMethodDecl(GoParser.MethodDeclContext ctx) {
        this.currentScope = this.currentScope.getEnclosingScope();
    }

    @Override
    public void enterBlock(GoParser.BlockContext ctx) {
        this.currentScope = this.scopes.get(ctx);
    }

    @Override
    public void exitBlock(GoParser.BlockContext ctx) {
        this.currentScope = this.currentScope.getEnclosingScope();
    }

    @Override
    public void enterTypeSpec(GoParser.TypeSpecContext ctx) {
        Type srcType = getType(SymbolTable.getEmbeddedType_(ctx.type_()));
        Type desType = Type.typeClone(ctx.IDENTIFIER().getText(), srcType);
        if (desType != null) {
            this.typeTable.put(desType.name, desType);
        }
    }

    @Override
    public void enterConstSpec(GoParser.ConstSpecContext ctx) {
        List<TerminalNode> vars = ctx.IDENTIFIER();

        if (ctx.type_() != null) {
            Type type = getType(SymbolTable.getEmbeddedType_(ctx.type_()));

            for (TerminalNode var : vars) {
                Symbol newSymbol = this.currentScope.search(var.getText());
                newSymbol.type = type;

                this.currentScope.define(newSymbol);
            }
        } else if (ctx.expressionList() != null) {
            List<GoParser.ExpressionContext> exprs = ctx.expressionList().expression();
            int varIndex = 0;

            for (GoParser.ExpressionContext expr : exprs) {
                List<Type> retTypes = getTypesFromExpression(expr);
                if (retTypes != null) {
                    for (Type retType : retTypes) {
                        if (varIndex == vars.size()) {
                            // error when too many results assign to fewer vars
                            error(expr.getStart(), "too many expressions");
                            break;
                        }
                        Symbol newSymbol = this.currentScope.search(vars.get(varIndex).getText());
                        newSymbol.type = retType;

                        this.currentScope.define(newSymbol);
                    }
                }
            }
        }
    }

    @Override
    public void enterVarSpec(GoParser.VarSpecContext ctx) {
        List<TerminalNode> vars = ctx.IDENTIFIER();
        if (ctx.type_() != null) {
            Type type = getType(SymbolTable.getEmbeddedType_(ctx.type_()));

            if (ctx.expressionList() != null) {
                List<GoParser.ExpressionContext> exprs = ctx.expressionList().expression();
                for (GoParser.ExpressionContext expr : exprs) {
                    if (getTypesFromExpression(expr) == null || getTypesFromExpression(expr).size() != 1 || Type.comparable(getTypesFromExpression(expr).get(0), type)) {
                        error(expr.getStart(), "type of " + expr.getText() + " is not equal to type, or " + expr.getText() + " has no return");
                    }
                }
            }

            for (TerminalNode var : vars) {
                Symbol newSymbol = this.currentScope.search(var.getText());
                newSymbol.type = type;

                this.currentScope.define(newSymbol);
            }
        } else {
            List<GoParser.ExpressionContext> exprs = ctx.expressionList().expression();
            int varIndex = 0;

            for (GoParser.ExpressionContext expr : exprs) {
                List<Type> retTypes = getTypesFromExpression(expr);
                if (retTypes != null) {
                    for (Type retType : retTypes) {
                        if (varIndex == vars.size()) {
                            // error when too many results assign to fewer vars
                            error(expr.getStart(), "too many expressions");
                            break;
                        }
                        Symbol newSymbol = this.currentScope.search(vars.get(varIndex).getText());
                        newSymbol.type = retType;

                        this.currentScope.define(newSymbol);
                    }
                }
            }
        }
    }

    @Override
    public void enterShortVarDecl(GoParser.ShortVarDeclContext ctx) {
        List<TerminalNode> vars = ctx.IDENTIFIER();
        List<GoParser.ExpressionContext> exprs = ctx.expressionList().expression();
        int varIndex = 0;

        for (GoParser.ExpressionContext expr : exprs) {
            List<Type> retTypes = getTypesFromExpression(expr);
            if (retTypes != null) {
                for (Type retType : retTypes) {
                    if (varIndex == vars.size()) {
                        // error when too many results assign to fewer vars
                        error(expr.getStart(), "too many expressions");
                        break;
                    }
                    Symbol newSymbol = this.currentScope.search(vars.get(varIndex).getText());
                    newSymbol.type = retType;

                    this.currentScope.define(newSymbol);
                }
            }
        }
    }


}
